#!/usr/bin/python

# InnerSpace
# Copyright (C) 1999-2006 Phil Christensen
#
# $Id: webclient.py 157 2007-02-11 18:37:33Z phil $
#
# See LICENSE for details

"""
Webclient

Stability: stable

This is the primary client for InnerSpace, replacing the
Java and Cocoa versions, saved for posterity.
"""

from nevow import appserver, inevow, loaders, athena, static, guard, rend, tags as T
from twisted.application import service, internet
from twisted.internet import reactor, defer
from twisted.python import failure

from zope.interface import implements

from inner.space import actions, auth, errors, editors

import os, os.path

class ClientLogin(rend.Page):
	implements(inevow.IResource)
	
	def __init__(self, webroot):
		super(ClientLogin, self).__init__()
		self.webroot = webroot
		self.docFactory = loaders.xmlfile(self.webroot + '/client-login.xml')
		
	def locateChild(self, ctx, segments):
		if(segments[0] == 'client.css'):
			return static.File(self.webroot + '/client.css'), ()
		return self, ()
	
	def render_loginaction(self, ctx, data):
		return guard.LOGIN_AVATAR


class AuthenticatedClient(rend.Page):
	implements(inevow.IResource)
	
	def __init__(self, webroot, user, login_code):
		super(AuthenticatedClient, self).__init__()
		self.webroot = webroot
		self.user = user
		self.login_code = login_code
		
	def locateChild(self, ctx, segments):
		if(segments[0]):
			possible_file = os.path.join(self.webroot, '/'.join(segments))
			if(os.access(possible_file, os.R_OK)):
				return static.File(possible_file), ()
			if(segments[0] == 'edit' and len(segments) > 1 ):
				if(segments[1] == 'entity'):
					return EntityEditor(self.webroot, self.user), segments[2:]
				if(segments[1] == 'verb'):
					return VerbEditor(self.webroot, self.user), segments[2:]
				if(segments[1] == 'property'):
					return PropertyEditor(self.webroot, self.user), segments[2:]
				if(segments[1] == 'acl'):
					return ACLEditor(self.webroot, self.user), segments[2:]
				if(segments[1] == 'code'):
					return SyntaxHighlightingTextArea(self.webroot, self.user), segments[2:]
		
		if(len(segments) == 1):
			self.login_code(self.user)
		client = ClientInterface(self.webroot, self.user)
		client.notifyOnDisconnect().errback = self.logout
		return client, segments
	
	def logout(self, *args, **kwargs):
		reactor.callLater(1, actions.logout_code, self.user)


class ClientInterface(athena.LivePage):	
	def __init__(self, webroot, user):
		super(ClientInterface, self).__init__()
		self.webroot = webroot
		self.user = user
		self.docFactory = loaders.xmlfile(self.webroot + '/client-interface.xml')
	
	def render_ClientConnector(self, ctx, data):
		f = ClientConnector(self.user)
		f.setFragmentParent(self)
		return ctx.tag[f]
	
	def locateChild(self, ctx, segments):
		if(segments[0]):
			return super(ClientInterface, self).locateChild(ctx, segments)
		return self, ()
	
	def action_call(self, ctx, requestId, method, objectID, args, kwargs):
		auth.user_activate(self.user)
		
		localObj = self._localObjects[objectID]
		
		try:
			func = localObj.locateMethod(ctx, method)
		except AttributeError:
			result = defer.fail(NoSuchMethod(objectID, method))
		else:
			result = defer.maybeDeferred(func, *args, **kwargs)
			
		def _cbCall(result, user):
			auth.user_activate(user)
			success = True
			if isinstance(result, failure.Failure):
				internal_error = result.trap(errors.UserError, errors.EngineError)
				if(internal_error == errors.UserError):
					print "User caused exception: " + repr(result.value)
					self.user._connection.write('<span class="error">' + str(result.value) + '</span>', None, None)
					return
				elif(internal_error == errors.EngineError):
					print self.user, "raised", str(result.value)
					self.user._connection.write('<span class="error">' + str(result.value) + '</span>', None, None)
					return
				log.msg("Sending error to browser:")
				log.err(result)
				success = False
				
				if result.check(LivePageError):
					result = (
						result.value.jsClass,
						result.value.args)
				else:
					result = (
						u'Divmod.Error',
						[u'%s: %s' % (
								result.type.__name__.decode('ascii'),
								result.getErrorMessage().decode('ascii'))])
			message = (u'respond', (unicode(requestId), success, result))
			self.addMessage(message)
			auth.user_deactivate()
				
		result.addBoth(_cbCall, self.user)
		
		auth.user_deactivate()


class ClientConnector(athena.LiveElement):
	jsClass = u'innerspace.ClientConnector'
	docFactory = loaders.stan(T.div(render=T.directive('liveElement'))[
		T.div(id='client-connector')
		])
	
	def __init__(self, user, *args, **kwargs):
		self.user = user
		self.user.set_connection(actions.RemoteWrapper(self))
		self.registry = auth.get_registry()
		
	def parse(self, command):
		actions.parse(self.user, auth.get_registry(), command)
	athena.expose(parse)
	
	def req_entity_editor(self, id):
		obj = self.registry.get(str(id))
		return editors.edit_entity(self.user, obj, request_id)
	athena.expose(req_entity_editor)
		
	def req_code_editor(self, id, verb):
		obj = self.registry.get(str(id))
		return editors.edit_verb(self.user, obj, str(verb), request_id)
	athena.expose(req_code_editor)
		
	def req_property_editor(self, id, prop):
		obj = self.registry.get(str(id))
		return editors.edit_property(self.user, obj, str(prop), request_id)
	athena.expose(req_property_editor)
		
	def req_acl_editor(self, id, item):
		obj = self.registry.get(str(id))
		if(item):
			obj = obj._vdict[str(item)]
		return editors.edit_acl(self.user, obj, request_id)
	athena.expose(req_acl_editor)
		
	def get_entity_attributes(self, id):
		obj = self.registry.get(str(id))
		return obj.get_details_unicode()
	athena.expose(get_entity_attributes)
		
	def remove_verb(self, id, verb):
		obj = self.registry.get(str(id))
		return obj.remove_verb(str(verb))
	athena.expose(remove_verb)
		
	def remove_property(self, id, prop):
		obj = self.registry.get(str(id))
		return obj.remove_property(str(prop))
	athena.expose(remove_property)

	def get_entity_attributes(self, id):
		"""
		Called remotely by the client to get info for an object.
		"""
		print "getting info for " + str(id)
		obj = self.registry.get(str(id))
		return actions.unicodify(obj.get_details_unicode())
	athena.expose(get_entity_attributes)


class EntityEditor(athena.LivePage):
	def __init__(self, webroot, user):
		super(EntityEditor, self).__init__()
		self.webroot = webroot
		self.user = user
		self.docFactory = loaders.xmlfile(self.webroot + '/entity-editor.xml')
	
	def locateChild(self, ctx, segments):
		if(segments):
			return super(EntityEditor, self).locateChild(ctx, segments)
		return self, ()

class VerbEditor(athena.LivePage):
	def __init__(self, webroot, user):
		super(VerbEditor, self).__init__()
		self.webroot = webroot
		self.user = user
		self.docFactory = loaders.xmlfile(self.webroot + '/verb-editor.xml')
	
	def locateChild(self, ctx, segments):
		if(segments):
			return super(VerbEditor, self).locateChild(ctx, segments)
		return self, ()

class PropertyEditor(athena.LivePage):
	def __init__(self, webroot, user):
		super(PropertyEditor, self).__init__()
		self.webroot = webroot
		self.user = user
		self.docFactory = loaders.xmlfile(self.webroot + '/property-editor.xml')
	
	def locateChild(self, ctx, segments):
		if(segments):
			return super(PropertyEditor, self).locateChild(ctx, segments)
		return self, ()

class ACLEditor(athena.LivePage):
	def __init__(self, webroot, user):
		super(ACLEditor, self).__init__()
		self.webroot = webroot
		self.user = user
		self.docFactory = loaders.xmlfile(self.webroot + '/acl-editor.xml')
	
	def locateChild(self, ctx, segments):
		if(segments):
			return super(ACLEditor, self).locateChild(ctx, segments)
		return self, ()

class SyntaxHighlightingTextArea(athena.LivePage):
	def __init__(self, webroot, user):
		super(SyntaxHighlightingTextArea, self).__init__()
		self.webroot = webroot
		self.user = user
		self.docFactory = loaders.xmlfile(self.webroot + '/editor-textarea.xml')
	
	def locateChild(self, ctx, segments):
		if(segments):
			return super(SyntaxHighlightingTextArea, self).locateChild(ctx, segments)
		return self, ()
